package proxy.practice;


import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * User:xuzhidian
 * Date:2018/4/3
 * Time:21:46
 */
public class MyProxy {
    private static final String ln = "\r\n";
    private final static File FILE = new File(App.class.getClassLoader().getResource("templates").getPath());

    public static Object newProxyInstance(MyClassLoader classLoader, Class<?>[] interfaces, MyInvocationHandler invocationHandler) throws Exception {
        //1.生成java代码
        String src = generateSrc(interfaces);
        //2.将java代码转成文件
        String path = MyProxy.class.getResource("").getPath();
        System.out.println(path);
        File f = new File(path + "$Proxy0.java");
        FileWriter fw = new FileWriter(f);
        fw.write(src);
        fw.flush();
        fw.close();
        //3.将java文件编译成class文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
        Iterable<? extends JavaFileObject> javaFileObjects = manager.getJavaFileObjects(f);
        JavaCompiler.CompilationTask task = compiler.getTask(null, null, null, null, null, javaFileObjects);
        task.call();
        manager.close();
        //4.装载到JVM中
        Class<?> $Proxy0 = classLoader.findClass("$Proxy0");
        Constructor<?> constructor = $Proxy0.getConstructor(MyInvocationHandler.class);
        if (f.exists()) {
            f.delete();
        }
        return constructor.newInstance(invocationHandler);
    }

    private static String generateSrc(Class<?>[] interfaces) {
        StringBuffer sb = new StringBuffer();
        sb.append("package proxy.practice;" + ln);
        sb.append("import proxy.IPerson;" + ln);
        sb.append("import java.lang.reflect.Method;" + ln);
        sb.append("public class $Proxy0 implements  " + interfaces[0].getName() + "{" + ln);
        sb.append("private MyInvocationHandler invocationHandler;" + ln);
        sb.append("public $Proxy0(MyInvocationHandler invocationHandler){" + ln);
        sb.append("this.invocationHandler=invocationHandler;" + ln);
        sb.append("}" + ln);
        for (Method method : interfaces[0].getMethods()) {
            sb.append("public " + method.getReturnType().getName() + " " + method.getName() + "(){" + ln);
            sb.append("try{" + ln);
            sb.append("Method m=" + interfaces[0].getName() + ".class.getMethod(\"" + method.getName() + "\",new Class[]{});" + ln);
            sb.append("this.invocationHandler.invoke(this,m,null);" + ln);
            sb.append("}catch(Throwable e){" + ln);
            sb.append("e.printStackTrace();" + ln);
            sb.append("}");
            sb.append("}");
        }

        sb.append("}" + ln);
        return sb.toString();
    }

    private static File generateJavaFile(Class<?> anInterface) {
        Configuration configuration = new Configuration();
        try {
            configuration.setDirectoryForTemplateLoading(FILE);
            Map<String, Object> dataMap = new HashMap<String, Object>();
            dataMap.put("packagePath", App.class.getPackage().getName());
            dataMap.put("className", "$Proxy1");
            dataMap.put("interfaceName", anInterface.getName());
            dataMap.put("invocationHandlerType", MyInvocationHandler.class.getName());
            dataMap.put("methods", anInterface.getMethods());
            Template template = configuration.getTemplate("proxy.ftl");
            File outFile = new File(App.class.getResource("").getPath() + "$Proxy1.java");
            Writer out = new BufferedWriter(new FileWriter(outFile));
            template.process(dataMap, out);
            return outFile;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TemplateException e) {
            e.printStackTrace();
        }
        return null;
    }
}
